﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

using Storm.TextEditor.Editor;
using Storm.TextEditor.Parser.Objects;
using Storm.TextEditor.Parser.Objects.Collections;

namespace Storm.TextEditor.Parser.XML
{
    /// <summary>
    /// Represents a language defined in an XML sheet.
    /// </summary>
    public class Language
    {
        #region Fields

        private string name = "";
        private string separators = " .,:;{}()[]+-*/\\ \t=&%$#@!|&";

        private long version = long.MinValue;

        private Hashtable blocks = new Hashtable();
        private Hashtable styles = new Hashtable();

        private Block mainBlock = null;
        private ArrayList fileTypes = new ArrayList();
        private LanguageCollection derivedLanguages = new LanguageCollection();

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the name of the Language.
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// Gets or sets the string that acts as separators.
        /// </summary>
        public string Separators
        {
            get { return separators; }
            set { separators = value; }
        }

        /// <summary>
        /// Gets the collection of derived languages.
        /// </summary>
        public LanguageCollection DerivedLanguages
        {
            get { return derivedLanguages; }
        }

        /// <summary>
        /// Gets a string that can be given to a Open File/Save File Dialog to filter files.
        /// </summary>
        public string FileDialogFilters
        {
            get
            {
                FileType currentFileType = null;
                StringBuilder stringBuilder = new StringBuilder();

                for (int fileTypeIndex = 0; fileTypeIndex < this.FileTypes.Count; fileTypeIndex++)
                {
                    currentFileType = this.FileTypes[fileTypeIndex] as FileType;

                    stringBuilder.Append(currentFileType.Name + " (*" + currentFileType.Extension + ")");
                    stringBuilder.Append("|");

                    stringBuilder.Append("*");
                    stringBuilder.Append(currentFileType.Extension);
                    stringBuilder.Append("|");
                }

                return stringBuilder.ToString();
            }
        }

        /// <summary>
        /// Gets or sets the version of the Language.
        /// </summary>
        public long Version
        {
            get { return version; }
            set { version = value; }
        }

        /// <summary>
        /// Gets or sets the block of code that is the main Block of the Language.
        /// </summary>
        public Block MainBlock
        {
            get { return mainBlock; }
            set { mainBlock = value; }
        }

        /// <summary>
        /// Gets the ArrayList of the file types.
        /// </summary>
        public ArrayList FileTypes
        {
            get { return fileTypes; }
        }

        /// <summary>
        /// Gets the blocks in the Language.
        /// </summary>
        public Block[] Blocks
        {
            get
            {
                blocks.Clear();
                this.FillBlocks(mainBlock);

                Block[] newBlocks = new Block[blocks.Values.Count];

                int count = 0;
                foreach (Block current in blocks.Values)
                {
                    newBlocks[count] = current;
                    count++;
                }

                return newBlocks;
            }
        }

        /// <summary>
        /// Gets the styles of the Language.
        /// </summary>
        public TextStyle[] Styles
        {
            get
            {
                styles.Clear();
                Block[] blocks = this.Blocks;
                foreach (Block block in blocks)
                {
                    styles[block.Style] = block.Style;

                    foreach (Scope current in block.ScopePatterns)
                    {

                        if (current.Style != null)
                            styles[current.Style] = current.Style;
                    }

                    foreach (PatternList current in block.KeywordsList)
                    {
                        if (current.Style != null)
                            styles[current.Style] = current.Style;
                    }

                    foreach (PatternList current in block.OperatorsList)
                    {
                        if (current.Style != null)
                            styles[current.Style] = current.Style;
                    }
                }

                TextStyle[] newStyles = new TextStyle[styles.Values.Count];

                int i = 0;
                foreach (TextStyle st in styles.Values)
                {
                    newStyles[i] = st;
                    i++;
                }

                return newStyles;
            }
        }

        #endregion

        #region Methods

        #region Public

        /// <summary>
        /// Updates all the child blocks and styles in the Language.
        /// </summary>
        public void UpdateLists()
        {
            Block[] blocks = Blocks;
            foreach (Block block in blocks)
            {
                block.Parent = this;
                block.ResetLookupTable();

                block.KeywordsList.Parent = block;
                foreach (PatternList patterns in block.KeywordsList)
                {
                    patterns.Parent = block.KeywordsList;

                    foreach (Pattern pattern in patterns)
                        block.AddToLookupTable(pattern);
                }

                block.OperatorsList.Parent = block;
                foreach (PatternList patterns in block.OperatorsList)
                {
                    patterns.Parent = block.OperatorsList;

                    foreach (Pattern pattern in patterns)
                        block.AddToLookupTable(pattern);
                }

                block.BuildLookupTable();
            }
        }

        /// <summary>
        /// Raises the version of the Language.
        /// </summary>
        public void ChangeVersion()
        {
            this.Version++;
            if (this.Version > long.MaxValue - 10)
                this.Version = long.MinValue;
        }

        /// <summary>
        /// Returns a new Language from the given language definition file.
        /// </summary>
        /// <param name="filename">Path to the file.</param>
        /// <returns>A new Language from the given language definition file.</returns>
        public static Language FromSyntaxFile(string filename)
        {
            LanguageReader loader = new LanguageReader();
            return loader.Load(filename);
        }

        /// <summary>
        /// Returns a new Language from the given language definition file.
        /// </summary>
        /// <param name="stream">File stream.</param>
        /// <returns>A new Language from the given language definition file.</returns>
        public static Language FromSyntaxFile(Stream stream)
        {
            LanguageReader loader = new LanguageReader();
            return loader.Load(stream);
        }

        /// <summary>
        /// Inserts the main block of the given Language into this Language's blocks.
        /// </summary>
        /// <param name="target">Target Language.</param>
        public void MergeByMainBlock(Language target)
        {
            Block[] blocks = this.Blocks;
            foreach (Block current in blocks)
                current.ChildBlocks.Insert(0, target.mainBlock);
        }

        /// <summary>
        /// Inserts the child blocks of the given Language into this Language's blocks.
        /// </summary>
        /// <param name="target">Target Language.</param>
        public void MergeByChildBlocks(Language target)
        {
            Block[] blocks = this.Blocks;
            foreach (Block current in blocks)
            {
                for (int i = target.mainBlock.ChildBlocks.Count - 1; i >= 0; i--)
                {
                    Block child = target.mainBlock.ChildBlocks[i];
                    current.ChildBlocks.Insert(0, child);
                }
            }
        }

        /// <summary>
        /// Saves the Language's styles in a configuration file.
        /// </summary>
        public void SaveStyles()
        {
            if (LanguageReader.UserCustomStyles == null)
                throw new IOException("Invalid user config dir.");
            else
            {
                if (Directory.Exists(LanguageReader.UserCustomStyles) == false)
                    throw new IOException("Invalid user config path name, or path don't exist.");
            }

            string path = Path.Combine(LanguageReader.UserCustomStyles, name + ".conf");

            XmlTextWriter xmlTextWriter = new XmlTextWriter(path, Encoding.UTF8);

            xmlTextWriter.Formatting = Formatting.Indented;
            xmlTextWriter.WriteStartElement("styles");

            TextStyle[] styles = Styles;
            foreach (TextStyle style in styles)
            {
                xmlTextWriter.WriteStartElement("Style");

                xmlTextWriter.WriteAttributeString("Name", style.Name);

                xmlTextWriter.WriteAttributeString("ForeColor", style.ForeColor.Name);
                xmlTextWriter.WriteAttributeString("BackColor", style.BackColor.Name);

                xmlTextWriter.WriteAttributeString("Bold", style.Bold.ToString());
                xmlTextWriter.WriteAttributeString("Italic", style.Italic.ToString());
                xmlTextWriter.WriteAttributeString("Underline", style.Italic.ToString());

                xmlTextWriter.WriteEndElement();
            }

            xmlTextWriter.WriteEndElement();
            xmlTextWriter.Flush();
            xmlTextWriter.Close();
        }

        /// <summary>
        /// Returns the Language as a string.
        /// </summary>
        /// <returns>The Language as a string.</returns>
        public override string ToString()
        {
            return name;
        }

        #endregion

        #region Private

        /// <summary>
        /// Fills the list of blocks in the Language from the given block.
        /// </summary>
        /// <param name="block">Block to fill.</param>
        private void FillBlocks(Block block)
        {
            if (block == null)
                return;

            if (blocks[block] != null)
                return;

            blocks[block] = block;

            foreach (Block current in block.ChildBlocks)
                this.FillBlocks(current);

            foreach (Scope current in block.ScopePatterns)
            {
                this.FillBlocks(current.SpawnBlockOnEnd);
                this.FillBlocks(current.SpawnBlockOnStart);
            }
        }

        #endregion

        #endregion

        /// <summary>
        /// Initializes a new instance of Language.
        /// </summary>
        public Language()
        {
        }
    }
}
